﻿// -----------------------------------------------------------------------
// <copyright file="TableDto.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;

namespace Centido.Core.Dto
{
    /// <summary>
    ///   A data transfer object for a table.
    /// </summary>
    public class TableDto : DataTransferObject
    {
        /// <summary>
        ///   The name of the xml tag that is used to store this object.
        /// </summary>
        public const string XmlTag = "table";

        /// <summary>
        ///   The current xml schema version.
        /// </summary>
        public const string CurrentXmlSchemaVersion = "1";

        /// <summary>
        ///   The name of the xml attribute to store the table name.
        /// </summary>
        public const string NameAttribute = "name";

        /// <summary>
        ///   The name of the xml attribute to store the table display name
        /// </summary>
        public const string DisplayNameAttribute = "displayName";

        /// <summary>
        ///   The name of the xml attribute to store the table description.
        /// </summary>
        public const string DescriptionAttribute = "description";

        /// <summary>
        ///   The name of the xml Attribute to store the need for unit level security.
        /// </summary>
        public const string HasUnitLevelSecurityAttribute = "hasUnitSecurity";

        /// <summary>
        ///   The name of the xml tag to store the column list.
        /// </summary>
        public const string ColumnListTag = "columns";

        /// <summary>
        ///   The name of the xml tag to store the index list.
        /// </summary>
        public const string IndexListTag = "indexes";

        /// <summary>
        ///   The name of the xml tag to store the table permissions.
        /// </summary>
        public const string PermissionListTag = "permissions";


        /// <summary>
        ///   The table columns.
        /// </summary>
        private readonly List<ColumnDto> _columns = new List<ColumnDto>();
        
        /// <summary>
        ///   The table indexes.
        /// </summary>
        private readonly List<IndexDto> _indexes = new List<IndexDto>();

        /// <summary>
        ///   The table permissions.
        /// </summary>
        private readonly List<TablePermissionDto> _permissions = new List<TablePermissionDto>();


        /// <summary>
        ///   Gets or sets the table name.
        /// </summary>
        public string Name { get; set; }


        /// <summary>
        ///   Gets or sets the table display name.
        /// </summary>
        public string DisplayName { get; set; }


        /// <summary>
        ///   Gets or sets the table description.
        /// </summary>
        public string Description { get; set; }


        /// <summary>
        ///   Gets or sets the need for unit level security.
        /// </summary>
        public bool? HasUnitLevelSecurity { get; set; }


        /// <summary>
        ///   Gets the table columns.
        /// </summary>
        public ICollection<ColumnDto> Columns
        {
            get { return _columns; }
        }


        /// <summary>
        ///   Gets the table indexes.
        /// </summary>
        public ICollection<IndexDto> Indexes
        {
            get { return _indexes; }
        }


        /// <summary>
        ///   Gets the table permissions.
        /// </summary>
        public ICollection<TablePermissionDto> Permissions
        {
            get { return _permissions; }
        }


        /// <summary>
        ///   Adds the specified column to the table column list.
        /// </summary>
        /// <param name="columnDto">The table column to be added.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnDto"/> is null.</exception>
        public void AddColumn(ColumnDto columnDto)
        {
            if (columnDto == null)
                throw new ArgumentNullException("columnDto");

            if (!_columns.Contains(columnDto))
                _columns.Add(columnDto);
        }


        /// <summary>
        ///   Deletes the specified column from the table column list.
        /// </summary>
        /// <param name="columnDto">The table column to deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnDto"/> is null.</exception>
        public void DeleteColumn(ColumnDto columnDto)
        {
            if (columnDto == null)
                throw new ArgumentNullException("columnDto");

            if (_columns.Contains(columnDto))
                _columns.Remove(columnDto);
        }


        /// <summary>
        ///   Adds the specified index to the table index list.
        /// </summary>
        /// <param name="indexDto">The table index to be added.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="indexDto"/> is null.</exception>
        public void AddIndex(IndexDto indexDto)
        {
            if (indexDto == null)
                throw new ArgumentNullException("indexDto");

            if (!_indexes.Contains(indexDto))
                _indexes.Add(indexDto);
        }


        /// <summary>
        ///   Deletes the specified index from the table index list.
        /// </summary>
        /// <param name="indexDto">The table index to be deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="indexDto"/> is null.</exception>
        public void DeleteIndex(IndexDto indexDto)
        {
            if (indexDto == null)
                throw new ArgumentNullException("indexDto");

            if (_indexes.Contains(indexDto))
                _indexes.Remove(indexDto);
        }


        /// <summary>
        ///   Adds the specified table permission to the table permission list.
        /// </summary>
        /// <param name="permission">The table permission to be added.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="permission"/> is null.</exception>
        public void AddPermission(TablePermissionDto permission)
        {
            if (permission == null)
                throw new ArgumentNullException("permission");

            if (!_permissions.Contains(permission))
                _permissions.Add(permission);
        }


        /// <summary>
        ///   Deletes the specified table permission from the table permission list.
        /// </summary>
        /// <param name="permission">The table permission to be deleted from the table permission list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="permission"/> is null.</exception>
        public void DeletePermission(TablePermissionDto permission)
        {
            if (permission == null)
                throw new ArgumentNullException("permission");

            if (_permissions.Contains(permission))
                _permissions.Remove(permission);
        }


        /// <summary>
        /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public override XmlSchema GetSchema()
        {
            return null;
        }


        /// <summary>
        /// Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="reader"/> is null.</exception>
        public override void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            reader.MoveToContent();

            // Reading the xml schema version
            var xmlSchemaVersion = ReadStringAttribute(reader, XmlSchemaVersionTag, isRequired: false);

            // If the xml schema version is not defined
            if (string.IsNullOrEmpty(xmlSchemaVersion)
                // or if it is the current xml schema version
                || xmlSchemaVersion == CurrentXmlSchemaVersion)
            {
                // Reading the table name
                Name = ReadStringAttribute(reader, NameAttribute);

                // Reading the display name
                DisplayName = ReadStringAttribute(reader, DisplayNameAttribute, isRequired: false);

                // Reading the description
                Description = ReadStringAttribute(reader, DescriptionAttribute, isRequired: false);

                // Reading the unit level security
                HasUnitLevelSecurity = ReadBoolAttribute(reader, HasUnitLevelSecurityAttribute, isRequired: false);

                // Clearing the column list
                _columns.Clear();

                // Clearing the index list
                _indexes.Clear();

                // Clearing the permission list
                _permissions.Clear();


                var isEmptyElement = reader.IsEmptyElement;

                reader.ReadStartElement();

                if (!isEmptyElement)
                {
                    // Reading columns, indexes and permissions
                    while (reader.LocalName == ColumnListTag
                        || reader.LocalName == IndexListTag
                        || reader.LocalName == PermissionListTag)
                    {
                        var currentXmlTag = reader.LocalName;

                        isEmptyElement = reader.IsEmptyElement;

                        reader.ReadStartElement();

                        if (isEmptyElement) continue;

                        switch (currentXmlTag)
                        {
                                // If table columns are specified
                            case ColumnListTag:
                                while (ColumnDto.IsColumnTag(reader.LocalName))
                                {
                                    var columnDto = ColumnDto.CreateNew(reader.LocalName);

                                    columnDto.ReadXml(reader);

                                    AddColumn(columnDto);
                                }
                                break;
                                // If table indexes are specified
                            case IndexListTag:
                                while (reader.LocalName == IndexDto.XmlTag)
                                {
                                    var indexDto = new IndexDto();

                                    indexDto.ReadXml(reader);

                                    AddIndex(indexDto);
                                }
                                break;
                                // If table permission are specified
                            case PermissionListTag:
                                while (reader.LocalName == TablePermissionDto.XmlTag)
                                {
                                    var permissionDto = new TablePermissionDto();

                                    permissionDto.ReadXml(reader);

                                    AddPermission(permissionDto);
                                }
                                break;
                        }

                        reader.ReadEndElement();
                    }

                    reader.ReadEndElement();
                }
            }
            else
                throw new ArgumentException(string.Format(
                    "The specified xml schema version is not supported (node = {0}, version = {1}).",
                    reader.Name,
                    xmlSchemaVersion));
        }


        /// <summary>
        ///   Converts the object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="writer"/> is null.</exception>
        public override void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(XmlTag);

            // The xml schema version
            writer.WriteAttributeString(XmlSchemaVersionTag, CurrentXmlSchemaVersion);

            // The table name
            if (!string.IsNullOrEmpty(Name))
                writer.WriteAttributeString(NameAttribute, Name);

            // The table display name
            if (!string.IsNullOrEmpty(DisplayName))
                writer.WriteAttributeString(DisplayNameAttribute, DisplayName);

            // The table description
            if (!string.IsNullOrEmpty(Description))
                writer.WriteAttributeString(DescriptionAttribute, Description);

            // The unit security
            if (HasUnitLevelSecurity.HasValue)
                writer.WriteAttributeString(HasUnitLevelSecurityAttribute,
                    HasUnitLevelSecurity.Value.ToString(CultureInfo.InvariantCulture));

            // The column list
            if (_columns.Count > 0)
            {
                writer.WriteStartElement(ColumnListTag);

                foreach (var columnDto in _columns)
                    columnDto.WriteXml(writer);

                writer.WriteEndElement();
            }

            // The index list
            if (_indexes.Count > 0)
            {
                writer.WriteStartElement(IndexListTag);

                foreach (var indexDto in _indexes)
                    indexDto.WriteXml(writer);

                writer.WriteEndElement();
            }

            // The permission list
            if (_permissions.Count > 0)
            {
                writer.WriteStartElement(PermissionListTag);

                foreach (var permissionDto in _permissions)
                    permissionDto.WriteXml(writer);

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }


        /// <summary>
        ///   Copies the properties of the data transfer object to the
        /// specified table.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        public void CopyTo(Table table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            // The table name
            if (!string.IsNullOrEmpty(Name) && Name != table.Name)
                table.Name = Name;

            // The table display name
            if (DisplayName != table.DisplayName)
                table.DisplayName = DisplayName;

            // The table description
            if (Description != table.Description)
                table.Description = Description;

            // The unit level security
            if (HasUnitLevelSecurity.HasValue && HasUnitLevelSecurity != table.HasUnitLevelSecurity)
                table.HasUnitLevelSecurity = HasUnitLevelSecurity.Value;
        }


        /// <summary>
        ///   Creates a data transfer object for the specified table.
        /// </summary>
        /// <param name="table">The specified table.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <returns>Returns the data transfer object for the specified table.</returns>
        public static TableDto CreateFrom(Table table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            var indexDto = new TableDto
            {
                Name = table.Name,
                DisplayName = table.DisplayName,
                Description = table.Description,
                HasUnitLevelSecurity = table.HasUnitLevelSecurity
            };

            return indexDto;
        }
    }
}
